package QianFeng08.CollectionDemo;

import java.util.*;

/**
 * Set接口
 * 一个不包含重复元素的collection.更确切地讲，set 不包含满足e1.equals(e2) 的元素对e1和e2,
 * 并且最多包含一个null元素。正如其名称所暗示的，此接口模仿了数学上的set抽象。
 *
 * 1、无序的（不保证顺序）
 * 2、不允许重复
 * 3、常用的具体实现类：HashSet、TreeSet、LinkedHashSet
 *
 * 如果要排序，选择TreeSet
 * 如果不要排序，也不用保证顺序选择HashSet
 * 不要排序，要保证顺序，选择LinkedHashSet
 * -------------------------------------------------------------------------
 * HashSet类：
 * 类实现Set接口，由哈希表(实际上是一个HashMap实例)支持。它不保证set的迭代顺序；
 * 特别是它不保证该顺序恒久不变(添加数据后顺序就会发生变化)。此类允许使用null元素。
 *
 * hashCode深入分析
 * hashcode()方法，在object类中定义 如下:
 * public native int hashCode();  相等的对象，它们的hashcode一定相等。
 * hashCode是本地方法，它的实现是根据本地机器相关，当然我们可以在自己写的类中覆盖hashcode()方法，
 * 比如String、Integer、 Double等等这些类都是覆盖了hashcode()方法的。
 *
 * 在java的集合中,判断两个对象是否相等的规测是:
 * (1)判断两个对象的hashCode是否相等
 *      如果不相等，认为两个对象也不相等，结束
 *      如果相等，转入2
 * (2)判断两个对象用equals运算是否相等
 *      如果不相等，认为两个对象也不相等
 *      如果相等，认为两个对象相等
 *      (equals(是判断两个对象是否相等的关键)
 * ----------------------------------------------------------------------------------------------
 * TreeSet类：
 * 基于TreeMap的NavigableSet实现。使用元素的自然顺序对元素进行排序，
 * 或者根据创建set时提供的Comparator进行排序，具体取决于使用的构造方法。
 *
 * 创建TreeSet集合时要实现排序接口，通过对象比较器实现：
 * 1、创建一个新类，继承Comparator<具体类>接口
 * 2、重写Comparator方法，自定义类的某个属性比较  列如：return o1.getAge() - o2.getAge();
 * 3、比较的属性最好要具有唯一性，要是添加的对象中有几个对象比较的属性相同，那么将不会添加进TreeSet数组中
 * --------------------------------------------------------------------------------------------
 * LinkedHashSet类：
 * public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, Serializable
 * 具有可预知迭代顺序的Set接口的哈希表和链接列表实现。此实现与HashSet的不同之外在于，
 * 后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序，
 * 即按照将元素插入到set中的顺序(插入顺序)进行迭代。注意，插入顺序不受在set中重新插入的元素的影响。
 * (如果在s.contains(e)返回true后立即调用s.add(e),则元素e会被重新插入到sets中。)
 *
 *
 **/
public class SetDemo {
    public static void main(String[] args) {
        //hashSet();
        //treeSet();
        linkedHashSet();
    }

    /**
     * HashSet
     * 1、实现原理：基于哈希表（HashMap）实现
     * 2、不允许重复，可以有一个null元素
     * 3、不保证顺序恒久不变
     * 4、添加元素时把元素作为HashMap的key存储，HashMap的value使用一个固定的object对象
     * 5、排除重复元素是通过equals来检测对象是否相同
     * 6、判断两个对象是否相同，先判断两个对象的hashCode是否相同
     *      （如果两对象hashCode相同，不一定是同一个对象，还要进行equals判断；不同一定不是同一对象）
     * 7、自定义对象要认为属性值都相同为同一个对象，那么要重写对象所在类的hashCode和equals方法
     * 小结：
     * （1）哈希表的存储结构：数组 + 链表 ，数组里的每个元素以链表的形式存储
     * （2）如何把对象存储到哈希表中，先计算对象的hashCode值，再对数组的长度求余数来决定对象存储在数组中的位置
     * （3）解决hashSet中重复使用值的方法是，参考第 7 点
     **/
    private static void hashSet() {
        Set<String> set = new HashSet<>();
        set.add("张飞");
        set.add("刘备");
        set.add("关羽");
        set.add("诸葛亮");
        set.add("赵云");
        System.out.println(set.size());
        //遍历数组  顺序是乱的
        String[] name = set.toArray(new String[]{});
        for (String s : name) {
            System.out.println(s);
        }

        Cat cat1 = new Cat("喵喵", 5, 1);
        Cat cat2 = new Cat("猫猫", 2, 2);
        Cat cat3 = new Cat("咪咪", 4, 3);
        Cat cat4 = new Cat("咪咪", 4, 3);
        Set<Cat> cats = new HashSet<>();
        cats.add(cat1);
        cats.add(cat2);
        cats.add(cat3);
        cats.add(cat3);//相同对象添加，不会成功添加进去
        cats.add(cat4);//cat3与cat4值相同，但是对象不同，可以添加进去
        System.out.println(cats.size());//4

        //重写了equals和hashCode方法后，不同对象但是值相同一样不能添加进数组中  hashCode绝对值相同
        for (Cat cat : cats) {
            System.out.println(cat);
        }
                                                          //重写前  重写后
        System.out.println("cat1=" + cat1.hashCode() % 16); //2      11
        System.out.println("cat2=" + cat2.hashCode() % 16); //13     15
        System.out.println("cat3=" + cat3.hashCode() % 16); //6      14
        System.out.println("cat4=" + cat4.hashCode() % 16); //5      14


    }

    /**
     * TreeSet
     * 1、有序的，基于TreeMap（二叉树数据结构），对象需要比较大小，通过对象比较器来实现
     * 2、对象比较器可以去除重复元素，如果自定义的数据类没有实现比较器接口，对象将无法添加到TreeSet集合中
     **/
    private static void treeSet() {
        //创建对象数组时，必须实现比较器，对象可以进行比较排序。comparator
        TreeSet<Cat> treeSet = new TreeSet<>(new CatComparator());
        Cat treeCat1 = new Cat("大佬", 5, 1);
        Cat treeCat2 = new Cat("大神", 2, 2);
        Cat treeCat3 = new Cat("菜鸟", 4, 3);
        Cat treeCat4 = new Cat("老手", 4, 4);
        //如果用来排序的属性值相同，则不会添加，所以一般排序选择具有唯一性的属性排序  第2点
        treeSet.add(treeCat1);
        treeSet.add(treeCat2);
        treeSet.add(treeCat3);
        treeSet.add(treeCat4);
        System.out.println(treeSet.size());
        //根据 id 排序输出
        for (Cat cat : treeSet) {
            System.out.println(cat);
        }
    }

    /**
     * LinkedHashSet
     * 1、哈希表和链接列表实现
     * 2、维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序，即按照将元素插入到set中的顺序(插入顺序)进行迭代。
     **/
    private static void linkedHashSet() {
        LinkedHashSet<Cat> linkedHashSet = new LinkedHashSet<>();
        Cat linkedCat1 = new Cat("大佬", 5, 1);
        Cat linkedCat2 = new Cat("大神", 2, 2);
        Cat linkedCat3 = new Cat("菜鸟", 4, 3);
        Cat linkedCat4 = new Cat("老手", 4, 4);
        linkedHashSet.add(linkedCat1);
        linkedHashSet.add(linkedCat2);
        linkedHashSet.add(linkedCat3);
        linkedHashSet.add(linkedCat4);

        for (Cat cat : linkedHashSet) {
            System.out.println(cat);
        }
    }


}
